package com.wayne.proxys;

import com.wayne.annotations.ApiServer;
import com.wayne.beans.MethodInfo;
import com.wayne.beans.ServerInfo;
import com.wayne.interfaces.ProxyCreator;
import com.wayne.interfaces.RestHandler;
import com.wayne.resthandler.WebClientRestHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 使用JDK动态代理实现代理类
 */
@Slf4j
public class JDKProxyCreator implements ProxyCreator {

    @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy:" + type);
        //根据接口提取服务器信息
        ServerInfo serverInfo = extractServerInfo(type);
        log.info("serverInfo:" + serverInfo);
        //给每一个代理类创建一个实现类
        RestHandler restHandler = new WebClientRestHandler();
        //初始化服务器信息(初始化web client)
        restHandler.init(serverInfo);
        return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //根据方法和参数得到调用信息
                MethodInfo methodInfo = extractMethodInfo(method, args);
                log.info("methodInfo:" + methodInfo);
                //调用rest
                return restHandler.invokeRest(methodInfo);
            }

            /**
             * 根据方法和参数提取方法调用信息
             * @param method
             * @param args
             * @return
             */
            private MethodInfo extractMethodInfo(Method method, Object[] args) {
                MethodInfo methodInfo = new MethodInfo();
                //设置请求的URL和请求方法
                extractUrlAndMethod(method, methodInfo);
                //设置请求参数和请求体
                extractRequestParameterAndBody(method, args, methodInfo);
                //设置返回对象信息
                extractReturnInfo(method, methodInfo);
                return methodInfo;
            }

            /**
             * 提取返回对象信息
             * @param method
             * @param methodInfo
             */
            private void extractReturnInfo(Method method, MethodInfo methodInfo) {
                //返回是Flux还是Mono
                //isAssignableFrom 判断类型
                //instanceof 判断实例
                boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
                methodInfo.setReturnFlux(isFlux);

                //得到返回对象的实际类型
                Class<?> elementType = extractElementType(method.getGenericReturnType());
                methodInfo.setReturnElementType(elementType);
            }

            /**
             * 提取返回对象类型
             * @param genericReturnType
             * @return
             */
            private Class<?> extractElementType(Type genericReturnType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                return (Class<?>) actualTypeArguments[0];
            }

            /**
             * 提取请求的param和body
             * @param method
             * @param args
             * @param methodInfo
             */
            private void extractRequestParameterAndBody(Method method, Object[] args, MethodInfo methodInfo) {
                Parameter[] parameters = method.getParameters();
                //参数和值对应的有序map
                Map<String, Object> params = new LinkedHashMap<>();
                methodInfo.setParams(params);
                for (int i = 0; i < parameters.length; i++) {
                    //判断是否含有PathVariable
                    PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class);
                    if (annoPath != null) {
                        params.put(annoPath.value(), args[i]);
                    }
                    //判断是否含有RequestBody
                    RequestBody annoBody = parameters[i].getAnnotation(RequestBody.class);
                    if (annoBody != null) {
                        methodInfo.setBody((Mono<?>) args[i]);
                        //请求Body对象的实际类型
                        methodInfo.setBodyElementType(extractElementType(parameters[i].getParameterizedType()));
                    }
                }
            }

            /**
             * 提取请求的URL和请求方法
             * @param method
             * @param methodInfo
             */
            private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation : annotations) {
                    //GET
                    if (annotation instanceof GetMapping) {
                        GetMapping a = (GetMapping) annotation;
                        methodInfo.setUrl(a.value().length > 0 ? a.value()[0] : "");
                        methodInfo.setMethod(HttpMethod.GET);
                    }
                    //POST
                    else if (annotation instanceof PostMapping) {
                        PostMapping a = (PostMapping) annotation;
                        methodInfo.setUrl(a.value().length > 0 ? a.value()[0] : "");
                        methodInfo.setMethod(HttpMethod.POST);
                    }
                    //PUT
                    else if (annotation instanceof PutMapping) {
                        PutMapping a = (PutMapping) annotation;
                        methodInfo.setUrl(a.value().length > 0 ? a.value()[0] : "");
                        methodInfo.setMethod(HttpMethod.PUT);
                    }
                    //DELETE
                    else if (annotation instanceof DeleteMapping) {
                        DeleteMapping a = (DeleteMapping) annotation;
                        methodInfo.setUrl(a.value().length > 0 ? a.value()[0] : "");
                        methodInfo.setMethod(HttpMethod.DELETE);
                    }
                }
            }
        });
    }

    /**
     * 根据类型提取服务器信息
     *
     * @param type
     * @return
     */
    private ServerInfo extractServerInfo(Class<?> type) {
        ApiServer apiServer = type.getAnnotation(ApiServer.class);
        return new ServerInfo(apiServer.value());
    }
}
